#include "StatementContext.h"
#include "FunctionContext.h"
#include "ExpressionContext.h"
#include "ScriptCompiler/Description/FunctionBodyDescription.h"
#include "ScriptCompiler/Description/VariableDescription.h"
#include "ScriptCompiler/Description/TypeDescription.h"
#include "ScriptCompiler/Description/ParameterDescription.h"
#include "ScriptCompiler/Description/FunctionDescription.h"
#include "ScriptCompiler/Description/DecoratedTypeDescription.h"


CStatementContext::CStatementContext(
    std::weak_ptr<CFunctionContext> InFunction,
    std::weak_ptr<CStatementContext> InParentSentence
    ,EStatementType InStatementType
    )
{
    Function = InFunction;
    ParentSentence= InParentSentence;
    Type = InStatementType;
}

std::shared_ptr<CFunctionContext> CStatementContext::GetFunction()
{
    return Function.lock();
}

std::shared_ptr<CVariableDescription> CStatementContext::OcuppyOrAddAnonymousVariable(SDecoratedTypeDescription InVaraibleType)
{
    auto& Variables = GetFunction()->FunctionBody->LocalVariables;

    for (auto& Variable : Variables)
    {
        if(!Variable->Name.IsEmpty())
        {
            continue;
        }

        if(!Variable->Type.IsSameWith(InVaraibleType))
        {
            continue;
        }

        if(IsAnonymousVariableOcuppied(Variable))
        {
            continue;
        }
        OcuppyAnonymousVariable(Variable);
        return Variable;
    }

    auto NewVariable = std::make_shared<CVariableDescription>();
    NewVariable->Type = InVaraibleType;
    NewVariable->Name = U"";
    NewVariable->Index = Variables.Num();
    Variables.Add(NewVariable);
    OcuppyAnonymousVariable(NewVariable);

    return NewVariable;
}

bool CStatementContext::IsAnonymousVariableOcuppied(std::shared_ptr<CVariableDescription> InVariable)
{
    assert(InVariable->Name.IsEmpty() && "this function only for anonymous variable!");

    if(OcuppiedAnonymousVariables.Contains(InVariable))
    {
        return true;
    }
    else if(!ParentSentence.expired())
    {
        return ParentSentence.lock()->IsAnonymousVariableOcuppied(InVariable);
    }

    return false;
}

std::shared_ptr<CStatementContext> CStatementContext::GetParentSentence()
{
    if(ParentSentence.expired())
    {
        return nullptr;
    }

    return ParentSentence.lock();
}

std::shared_ptr<CExpressionContext> CStatementContext::NewExpression(std::shared_ptr<CExpressionContext> InParentExpression)
{
    return std::make_shared<CExpressionContext>(shared_from_this(),InParentExpression);
}

// std::shared_ptr<CVariableDescription> CStatementContext::AddVariable(SDecoratedTypeDescription InVaraibleType,const CString& InVariableName)
// {


// }

TOptional<SScriptFilePosition> CStatementContext::TestVariableName(const CString& InVariableName)
{
    assert(!InVariableName.IsEmpty() && "this function not design for anonymous variable!");
    
    //test is variable have same name with Function argument
    auto& Parameters = GetFunction()->Parameters;
    for (auto& Parameter : Parameters)
    {
        if(Parameter->Name!=InVariableName)
        {
            continue;
        }

        return Parameter->PositionInFile;
    }


    auto& Variables = GetFunction()->FunctionBody->LocalVariables;

    for (auto& Variable : Variables)
    {
        if(Variable->Name!=InVariableName)
        {
            continue;
        }
        
        auto This= shared_from_this();

        assert(This!=Variable->DeclareSentence
            && "this sentence can not declare this variable twice!"
        );
        
        //test is exist variable on the same sentence tree branch with this sentence
        //1. test is this sentence is child of parent of declaration sentence of exist variable
        auto ParentSentence = This;
        auto Target = Variable->DeclareSentence->GetParentSentence();
        while(true)
        {
            if(!ParentSentence)
            {
                break;
            }

            if(ParentSentence==Target)
            {
                return Variable->PositionInFile;
            }

            ParentSentence = ParentSentence->GetParentSentence();
        }

        //2. test is exist variable's declareSentence is child of parent of this sentence
        ParentSentence = Variable->DeclareSentence;
        Target = This->GetParentSentence();
        while(true)
        {
            if(!ParentSentence)
            {
                break;
            }

            if(ParentSentence==Target)
            {
                return Variable->PositionInFile;
            }

            ParentSentence = ParentSentence->GetParentSentence();
        }

    }

    //no pre-defined variable 
    return TOptional<SScriptFilePosition>();
}

    
std::shared_ptr<CVariableDescription> CStatementContext::FindVariableByName(const CString &InVariableName)
{
    auto& Variables = GetFunction()->FunctionBody->LocalVariables;

    for (auto& Variable : Variables)
    {
        if(Variable->Name!=InVariableName)
        {
            continue;
        }

        //1. test is this sentence is child of parent of declaration sentence of exist variable
        auto ParentSentence = shared_from_this();
        auto Target = Variable->DeclareSentence->GetParentSentence();

        while(true)
        {
            if(!ParentSentence)
            {
                break;
            }

            if(ParentSentence==Target)
            {
                return Variable;
            }

            ParentSentence = ParentSentence->GetParentSentence();
        }
    }

    return nullptr;
}

std::shared_ptr<CParameterDescription> CStatementContext::FindParameterByName(const CString &InParameterName)
{
    return GetFunction()->FindParameterByName(InParameterName);
}

std::shared_ptr<CGSharpFieldDescription> CStatementContext::FindFieldByName(const CString &InFieldName)
{
    return GetFunction()->GetOwnerClass()->FindFieldByName(InFieldName);
}

TVector<std::shared_ptr<CFunctionDescription>> CStatementContext::FindFunctionsByName(const CString& InFieldName)
{
    return GetFunction()->GetOwnerClass()->FindFunctions(InFieldName);
}


std::shared_ptr<CPropertyDescription> CStatementContext::FindPropertyByName(const CString &InPropertyName)
{
    return GetFunction()->GetOwnerClass()->FindProperty(InPropertyName);
}

std::shared_ptr<CTypeDescription> CStatementContext::FindClassByName(const CString &InClassName)
{
    return GetFunction()->GetOwnerClass()->FindClassInFile(GetFunction()->FileContext
        , InClassName
    );
}

std::shared_ptr<CFunctionDescription> CStatementContext::FindFunctionByNameAndType(const CString &InFunctionName, SDecoratedTypeDescription InFunctionType)
{
    return GetFunction()->GetOwnerClass()->FindFunctionByNameAndType(InFunctionName,InFunctionType);
}

bool CStatementContext::IsPosibleFunction(const CString &InFunctionName)
{
    return GetFunction()->GetOwnerClass()->IsPosibleFunction(InFunctionName);
}

void CStatementContext::OcuppyAnonymousVariable(std::shared_ptr<CVariableDescription> InVariable)
{
    assert(InVariable->Name.IsEmpty() && "this function only for anonymous variable!");
    OcuppiedAnonymousVariables.Add(InVariable);
}

std::shared_ptr<CVariableDescription> CStatementContext::AddVariable(SDecoratedTypeDescription InVaraibleType, const CString &InVariableName)
{
    std::shared_ptr<CVariableDescription> Variable= std::make_shared<CVariableDescription>();
    Variable->Type = InVaraibleType;
    Variable->Name = InVariableName;
    Variable->DeclareSentence = shared_from_this();
    GetFunction()->FunctionBody->AddLocalVariable(Variable);

    return Variable;
}

std::shared_ptr<CNamespaceDescription> CStatementContext::FindNamespaceByName(const CString &InNamespaceName)
{
    return GetFunction()->GetOwnerClass()->FindNamespaceInFile(GetFunction()->FileContext
        , InNamespaceName
    );
}

